
import java.io.*;
import java.io.FileReader;
import java.io.FileWriter;
import java.util.*;

public class CryptAnalyzer {

	private static final String currentDirectory = System.getProperty("user.dir") + "/";
	private static final String cipherTextDirectory = currentDirectory + "cipherText/";
    private Vector<Character[][]> potentialPads;
    private Vector<Character[][]> pads;
    private Vector<String> cipherTextList;
    private int messageCount;
    private int numberOfRows = 50;
    private int numberOfColumns = 80;
    private Decrypter d;

    public CryptAnalyzer(String cipherFilenameList) {

        potentialPads = new Vector<Character[][]>();
        pads = new Vector<Character[][]>();
        cipherTextList = new Vector<String>();
        messageCount = 1 + cipherFilenameList.length() - cipherFilenameList.replaceAll("\\,", "").length();
        for (String cipherTextFilename : cipherFilenameList.split(",")) {
            try {
                StringBuffer fileData = new StringBuffer(4000);
                BufferedReader reader = new BufferedReader(
                    new FileReader(cipherTextDirectory + "/" + cipherTextFilename)
                );
                char[] buf = new char[4000];
                String readerContents = reader.readLine();
             //   assert(readerContents.length() == 4000);
                buf = readerContents.toCharArray();
                
                // i don't need buf. i'll just use readerContents
                
                cipherTextList.add(readerContents);
                //messageCount++;
                
                reader.close();
            } catch (Exception e) {
                System.err.println("ERROR: " + e);
            }
        }
    }

    public void retrievePads() {
    	
    	String[] pads = new String[16];
        
        boolean foundCorrectPads = false;
        boolean badPad = false;
        
    	//make an array to hold the decrypted messages
        String[] decryptedMessages = new String[cipherTextList.size()];
        
        //Now we have a list of plain messages. we need to see how many times ' ' pops up.
        int[] charCount = new int[127];
        int currentChar= -1;
        
        //Loop through all of the possible pad numbers
        //Potential bug: if cipherTextList.length % padCount != 0, there will be issues
        for(int padCount = 1; padCount <= 16 && foundCorrectPads == false; padCount++){
        	//find the potential pads for the possible number
        	pads[padCount] = getPotentialPads(padCount);
        	//loop thru each of the pads
        	for(int padIndex = 0; padIndex < pads.length; padIndex++){
        		//the message index is set to padIndex, because of the round robin.  the first time a pad is used depends on the number of pads.
        		//I increase the message index by the number of pads in order to get every nth pad where n is the number of pads.
        		for(int messageIndex = padIndex; messageIndex < cipherTextList.size(); messageIndex += pads.length){
        			//using the current pad, decrypt the message
        			for(int i=0; i<50*80; i++){
        				decryptedMessages[messageIndex] = d.decrypt(pads[padIndex], cipherTextList.elementAt(messageIndex));
        			}
        		}
        	}
        	for(int i=1; i <= 4000 && badPad == false; i++){
        		for(int messageIndex = 0; messageIndex <= decryptedMessages.length; messageIndex++){
        			currentChar = (int)decryptedMessages[messageIndex].charAt(i-1);
        			charCount[currentChar] += 1;
        		}
        		//If the most common letter in all of the messages is ' ', then assume we have a legit pad
        		if(' ' != getMostCommonChar(charCount)){
        			badPad = true;
        		}
        	}
        	foundCorrectPads = !badPad;
        }
        for(int i=0; i<16; i++){
        	if(pads[i] != null)
        		outputPad("padFilesRecovered", pads[i]+".txt");
        }
        
    }

    /*
    Algorithm:
    * for each character position, 1-4000, find the most commonly occurring ciphertext
    character.
    * assume that the plaintext for that character is 'e'
    * run getSinglePad() for each character position, using the most common ciphertext
    character and 'e'
    */
    public String getPotentialPads(int assumedPadCount) {
        String potentialPad = "";
        //int[] charCount = new int[127];
        int currentChar;
        for (int i = 1; i <= 4000; i++) {
        	int[] charCount = new int[127];
        	/* -- uncomment later
            if (i % 80 == 0) {
                // the last column of pad is a space
                potentialPad += " ";
                continue;
            }
            */
            for (int j = assumedPadCount; j <= messageCount; j += assumedPadCount) {
                currentChar = (int) cipherTextList.elementAt(j-1).charAt(i-1);
                charCount[currentChar] += 1;
            }

            // call getSinglePad, then add the result to potentialPad
            char mostCommonChar = getMostCommonChar(charCount);
            potentialPad += getSinglePad(mostCommonChar, ' ');
        }
        // for debugging
        //   assert(potentialPad.length() == 4000);
        return potentialPad;
    }

    public void outputPad(String outputFilename, String pad) {
        String currentDirectory = System.getProperty("user.dir") + "/";
        try {
            FileWriter fw = new FileWriter(currentDirectory + "/" + outputFilename);
            fw.write(pad);
            fw.close();
        } catch (Exception e) {
            System.err.println("ERROR: " + e);
        }
    }

    public String getSinglePad(char cipherChar, char plainText) {
        char pad = 0;
        // guess 1
        pad = (char) ((int) cipherChar - 32 + 64 - (int) plainText);
        if (isPrintableASCII(pad)) {
            return Character.toString(pad);
        }
        // guess 2
        pad = (char) (cipherChar - 32 + 95 + 64 - plainText);
        return Character.toString(pad);
    }

    // helper methods
    private boolean isPrintableASCII(char c) {
        if (((int) c) >= 32 && ((int) c) <= 126) {
            return true;
        }
        return false;
    }

    private char getMostCommonChar(int[] charCount) {
        int max = 0;
        int maxIndex = 0;

        for (int i = 0; i < charCount.length; i++) {
            if (charCount[i] > max) {
                max = charCount[i];
                maxIndex = i;
            }
        }

        return (char) maxIndex;
    }
}
